/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

#include "common.h"


using namespace Scenic::Renderer;


VertexStore::VertexStore()
{
	default_tu2 = default_tv2 = 0.0;
	tm = NULL;
	empty();
}

VertexStore::VertexStore(int type, Context *context, Affine2 *tm, ClipArea *clip)
{
	default_tu2 = default_tv2 = 0.0;
	empty();
	init(type, context, tm, clip);
}

void VertexStore::init(int type, Context *context, Affine2 *tm, ClipArea *clip)
{
	setType(type);
	this->context = context;
	this->tm = tm;
	numVertices = 0;
	if(!clip)
		clip = context->clip;
	clipRect.left = clip->x;
	clipRect.top = clip->y;
	clipRect.right = clip->x + clip->width;
	clipRect.bottom = clip->y + clip->height;
	empty();
}

void VertexStore::setContext(Context *context)
{
	if(!context)
		return;
	ClipArea *clip = context->clip;

	this->context = context;
	clipRect.left = clip->x;
	clipRect.top = clip->y;
	clipRect.right = clip->x + clip->width;
	clipRect.bottom = clip->y + clip->height;
}

void VertexStore::setTransform(Affine2 *tm)
{
	this->tm = tm;
}

void VertexStore::setType(int type)
{
	this->type = type;
	numTextures = type & 3;
	numDimensions = (type & 4) ? 3 : 2;
	vertexSize = 4 * 4 + numTextures * numDimensions * sizeof(float);
}

void VertexStore::empty()
{
	vertices.size = 0;
	numVertices = 0;
	indices.size = 0;
	offset = 0;
}

void VertexStore::setDefaultTex2(float tu2, float tv2)
{
	default_tu2 = tu2;
	default_tv2 = tv2;
}

int VertexStore::addVertex(double x, double y, int diffuse)
{
	ClipArea *clip = context->clip;
	float *v = (float*)vertices.addPointer(vertexSize);

	if(tm)
	{
		*(v++) = (float)(tm->m00 * x + tm->m01 * y + tm->dx - clip->offsetX);
		*(v++) = (float)(tm->m10 * x + tm->m11 * y + tm->dy - clip->offsetY);
	}
	else
	{
		*(v++) = (float)(x - clip->offsetX);
		*(v++) = (float)(y - clip->offsetY);
	}
	*(v++) = 0.0;
	*(int*)(v++) = diffuse;

	numVertices++;
	return numVertices - 1 - offset;
}

int VertexStore::addVertex(double x, double y, int diffuse, float tu1, float tv1)
{
	ClipArea *clip = context->clip;
	float *v = (float*)vertices.addPointer(vertexSize);
	float tex[2][3];
	int i, j;

	if(tm)
	{
		*(v++) = (float)(tm->m00 * x + tm->m01 * y + tm->dx - clip->offsetX);
		*(v++) = (float)(tm->m10 * x + tm->m11 * y + tm->dy - clip->offsetY);
	}
	else
	{
		*(v++) = (float)(x - clip->offsetX);
		*(v++) = (float)(y - clip->offsetY);
	}
	*(v++) = 0.0;
	*(int*)(v++) = diffuse;

	if(numTextures == 1)
	{
		*(v++) = tu1;
		*(v++) = tv1;
	}
	else
	{
		tex[0][0] = tu1;
		tex[0][1] = tv1;
		tex[0][2] = 0.0f;
		tex[1][0] = default_tu2;
		tex[1][1] = default_tv2;
		tex[1][2] = 0.0f;
		
		for(i = 0; i < numTextures; i++)
		{
			for(j = 0; j < numDimensions; j++)
			{
				*(v++) = tex[i][j];
			}
		}
	}
	numVertices++;
	return numVertices - 1 - offset;
}

int VertexStore::addVertex(double x, double y, int diffuse, float tu1, float tv1, float tu2, float tv2)
{
	ClipArea *clip = context->clip;
	float *v = (float*)vertices.addPointer(vertexSize);
	float tex[2][3];
	int i, j;

	if(tm)
	{
		*(v++) = (float)(tm->m00 * x + tm->m01 * y + tm->dx - clip->offsetX);
		*(v++) = (float)(tm->m10 * x + tm->m11 * y + tm->dy - clip->offsetY);
	}
	else
	{
		*(v++) = (float)(x - clip->offsetX);
		*(v++) = (float)(y - clip->offsetY);
	}
	*(v++) = 0.0;
	*(int*)(v++) = diffuse;

	tex[0][0] = tu1;
	tex[0][1] = tv1;
	tex[0][2] = 0.0f;
	tex[1][0] = tu2;
	tex[1][1] = tv2;
	tex[1][2] = 0.0f;

	for(i = 0; i < numTextures; i++)
	{
		for(j = 0; j < numDimensions; j++)
		{
			*(v++) = tex[i][j];
		}
	}

	numVertices++;
	return numVertices - 1 - offset;
}

int VertexStore::addVertex(VERTEX_INFO *vertex)
{
	ClipArea *clip = context->clip;
	float *v = (float*)vertices.addPointer(vertexSize);
	int i, j;

	if(tm)
	{
		*(v++) = (float)(tm->m00 * vertex->p[0] + tm->m01 * vertex->p[1] + tm->dx - clip->offsetX);
		*(v++) = (float)(tm->m10 * vertex->p[0] + tm->m11 * vertex->p[1] + tm->dy - clip->offsetY);
	}
	else
	{
		*(v++) = (float)(vertex->p[0] - clip->offsetX);
		*(v++) = (float)(vertex->p[1] - clip->offsetY);
	}
	*(v++) = 0.0;
	*(int*)(v++) = vertex->diffuse;

	for(i = 0; i < numTextures; i++)
	{
		for(j = 0; j < numDimensions; j++)
		{
			*(v++) = vertex->tex[i][j];
		}
	}

	numVertices++;
	return numVertices - 1 - offset;
}

void VertexStore::addTriangle(int a, int b, int c)
{
	indices.add(a + offset);
	indices.add(b + offset);
	indices.add(c + offset);
}

void VertexStore::endBlock()
{
	offset = numVertices;
}

void fastCopy(void *dst, void *src, int size)
{
	int *d = (int*)dst;
	int *s = (int*)src;
	int l = size / 4;
	int i;

	for(i = 0; i < l; i++)
		d[i] = s[i];
}

static VertexDeclaration *getVertexDeclaration(int nt, int nd)
{
	static VertexDeclaration *cache[3][3];

	if(!cache[nd][nt])
	{
		ElementInfo em[10];
		int i = 0, j;
		
		em[i++] = ElementInfo(ATTRIBUTE_TYPE_FLOAT, 3, POSITION_ATTRIBUTE);
		em[i++] = ElementInfo(ATTRIBUTE_TYPE_CHAR, 4, COLOR_ATTRIBUTE);
		for(j = 0; j < nt; j++)
			em[i++] = ElementInfo(ATTRIBUTE_TYPE_FLOAT, nd, TEXCOORD_ATTRIBUTE, j);
		em[i++] = ElementInfo();
		
		cache[nd][nt] = new VertexDeclaration(em);
	}

	return cache[nd][nt];
}

static VertexBuffer *getVertexBuffer(Device *device, int size)
{
	static VertexBuffer *cache[40][100];
	static int counter = 0;
	int i = 0, s = 16;

	while(s < size)
	{
		s = s + s;
		i++;
	}

	counter = (counter + 1) % min(20, (1 + 1000000 / s));
	if(!cache[i][counter])
	{
		cache[i][counter] = device->createVertexBuffer(s);
	}

	return cache[i][counter];
}

static IndexBuffer *getIndexBuffer(Device *device, int size)
{
	static IndexBuffer *cache[40][100];
	static int counter = 0;
	int i = 0, s = 16;

	while(s < size)
	{
		s = s + s / 2;
		i++;
	}

	counter = (counter + 1) % min(10, (1 + 1000000 / s));
	if(!cache[i][counter])
	{
		cache[i][counter] = device->createIndexBuffer(s);
	}

	return cache[i][counter];
}

void VertexStore::draw()
{
// 	ENTER(VertexStore::draw);

	STAT(Vertices, numVertices);
	STAT(Indices, indices.size);
/*	VertexDeclaration *vd = getVertexDeclaration(numTextures, numDimensions);
	int vs = vd->getSize();
	static VertexBuffer *vb = NULL; 
	static IndexBuffer *ib = NULL;
	void *data;
	
	vb = getVertexBuffer(context->device, numVertices * vs);
	ib = getIndexBuffer(context->device, indices.size * 4);

	{
	ENTER(VertexStore::draw.vertexLock);
	data = vb->lock(0, numVertices * vs);
	}
	fastCopy(data, vertices.data, numVertices * vs);
	vb->unlock();

	{
	ENTER(VertexStore::draw.indexLock);
	data = ib->lock(0, indices.size * 4);
	}
	fastCopy(data, indices.data, indices.size * 4);
	ib->unlock();
	{
	ENTER(VertexStore::draw.3);
	context->device->draw(vd, vb, ib, 0, indices.size / 3);
	} */

#ifdef DIRECTX
	int fvf = D3DFVF_XYZ | D3DFVF_DIFFUSE;
	
	if(numTextures == 0)
		fvf |= D3DFVF_TEX0;
	else if(numTextures == 1)
		fvf |= D3DFVF_TEX1;
	else if(numTextures == 2)
		fvf |= D3DFVF_TEX2;

	if(numDimensions == 3)
	{
		for(int i = 0; i < numTextures; i++)
			fvf |= D3DFVF_TEXCOORDSIZE3(i);
	}
	context->device->getHandle()->SetFVF(fvf);
	if(Device::getCaps().d3dcaps.MaxVertexIndex > 0xffff && 
		Device::getCaps().d3dcaps.MaxVertexIndex >= (unsigned int)numVertices)
	{
		context->device->getHandle()->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 
			0, numVertices,
			indices.size / 3,
			indices.data, D3DFMT_INDEX32,
			vertices.data, vertexSize); 
	}
	else
	{
		char *temp = new char[vertexSize * 300];
		int i;

		for(i = 0; i < indices.size; i += 300)
		{
			int l = min(300, indices.size - i);
			int j;

			for(j = 0; j < l; j++)
			{
				memcpy(temp + j * vertexSize, 
					vertices.data + indices.data[i + j] * vertexSize,
					vertexSize);
			}
			context->device->getHandle()->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 
				l / 3, 
				temp,
				vertexSize);
		}

		delete temp;
	}
#endif
#ifdef OPT_OPENGL
	glVertexPointer(3, GL_FLOAT, vertexSize, vertices.data);
	glEnableClientState(GL_VERTEX_ARRAY);
	glColorPointer(4, GL_UNSIGNED_BYTE, vertexSize, vertices.data + 4 * 3);
	glEnableClientState(GL_COLOR_ARRAY);

	if(numTextures >= 1)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glClientActiveTextureARB(GL_TEXTURE0_ARB);
		glTexCoordPointer(numDimensions, GL_FLOAT, vertexSize, vertices.data + 4 * 3 + 4);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	if(numTextures >= 2)
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glClientActiveTextureARB(GL_TEXTURE1_ARB);
		glTexCoordPointer(numDimensions, GL_FLOAT, vertexSize, vertices.data + 4 * 3 + 4 + 4 * numDimensions);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	context->device->makeCurrent();
	glDrawElements(GL_TRIANGLES, indices.size, GL_UNSIGNED_INT, indices.data);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE1_ARB);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
#endif
}
